/*
 * Licensed to the Tai Ping Jin Ke
 *
 * Copyright (c) 2022 .
 * All rights reserved.
 * 项目名称：太平金科-通用能力封装层-公共组件
 * 版权说明：本软件属太平金融科技服务(上海)有限公司所有，在未获得太平金融科技服务(上海)有限公司正式授权情况下，任何企业和个人，不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。
 */
package com.taipingframework.utility.thread.threadpool;

import cn.hutool.json.JSONUtil;
import com.taipingframework.utility.thread.threadfactory.SmartThreadFactory;
import com.taipingframework.utility.thread.properties.ThreadPoolProperty;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.lang.NonNull;
import org.springframework.scheduling.annotation.AsyncConfigurer;

import java.lang.reflect.Method;
import java.util.concurrent.Executor;

/**
 * 在Spring Boot应用中实现异步任务，用@Async注解标记的方法，称为异步方法。
 * (1) 调用异步方法类上或者启动类加上注解 @EnableAsync；
 * (2) 在需要被异步调用的方法外加上 @Async；
 * (3) 所使用的 @Async 注解方法的类对象应该是Spring容器管理的bean对象。
 * (4) java中的异步方法通常返回Future<T>类型。
 * <p>
 * 所谓异步任务，其实就是异步执行程序，有些时候遇到一些耗时的的任务，
 * 如果一直卡等待，肯定会影响其他程序的执行，所以就需要这些程序以异步的方式去执行。
 * <p>
 * Spring异步线程池的接口类是TaskExecutor，本质还是java.util.concurrent.Executor，
 * 没有配置的情况下，默认使用的是 SimpleAsyncTaskExecutor。
 * <p>
 * SimpleAsyncTaskExecutor的特点是，每次执行任务时，它会重新启动一个新的线程，
 * 并允许开发者控制并发线程的最大数量（concurrencyLimit），从而起到一定的资源节流作用。
 * 默认是concurrencyLimit取值为-1，即不启用资源节流。
 * <p>
 * Spring默认使用的线程池是 SimpleAsyncTaskExecutor，但是，Spring更加推荐我们开发者使用 ThreadPoolTaskExecutor 类来创建线程池，
 * 其本质是对 java.util.concurrent.ThreadPoolExecutor 的包装。
 * <p>
 * 此处，我们重写 Spring 的默认线程池。这样，在使用 注解@Async 实现异步方法时，无需通过该注解的参数显式地指定 执行异步方法 使用的线程池，
 * 即，无需注解@Async声明使用哪个线程池。
 * <p>
 * ● 两种实现方式：
 * public class NativeAsyncTaskExecutePool implements AsyncConfigurer { ... }
 * public class NativeAsyncTaskExecutePool extends AsyncConfigurerSupport { ... }
 */
@Slf4j
public class NativeAsyncTaskExecutePool implements AsyncConfigurer {

    private ThreadPoolProperty threadPoolProperty;

    public NativeAsyncTaskExecutePool(ThreadPoolProperty threadPoolProperty) {
        this.threadPoolProperty = threadPoolProperty;
    }

    /**
     * 使用我们自定义的线程池
     * <p>
     * 此处可供使用的线程池有两个：
     * ■ org.springframework.scheduling.concurrent.ConcurrentTaskExecutor
     * ■ org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor （推荐使用，它已经提供了一个比较好的可拓展性）
     */
    @Override
    public Executor getAsyncExecutor() {
        SmartThreadFactory threadFactory = new SmartThreadFactory(threadPoolProperty.getThreadPrefix());
        return ThreadPoolHelper.createAsyncExecutor(threadPoolProperty.getCorePoolSize(),
                threadPoolProperty.getMaxPoolSize(),
                threadPoolProperty.getKeepAliveTime(),
                threadPoolProperty.getQueueCapacity(),
                threadFactory);
    }

    /**
     * 异步任务中异常处理
     */
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (@NonNull Throwable throwable, @NonNull Method method, @NonNull Object... objects) -> {
            String packagePathName = method.getDeclaringClass().getGenericSuperclass().getTypeName();
            String methodName = method.getName();
            log.error("执行 {}.{} 方法时发生异常，方法入参为 {}", packagePathName, methodName, JSONUtil.toJsonStr(objects), throwable);
        };
    }

}
